package org.yacon;

import java.util.*;

import static org.yacon.ConsoleEngineConstants.*;

/**
 * Performs parsing of User defined command to command name and set of properties
 *
 * @author Evgeny Vetrov
 */
public class CommandParser {
    private final String command;
    private final String cmdName;
    private final Map<String, Object> parameterValues;


    private CommandParser(String command, Map<String, TypeConverter> converters) {
        if (command == null)
            throw new NullPointerException("Command can't be null");
        this.command = command;
        Map<String, Object> parameterValues = new HashMap<String, Object>();
        String cmdName = parse(converters, command, parameterValues);
        this.cmdName = (cmdName == null || cmdName.isEmpty()) ? HELP : cmdName;
        this.parameterValues = Collections.unmodifiableMap(parameterValues);
    }

    /**
     * Creates CommandParser and performs parsing of User defined <code>command</code>, values of all parameters will
     * be of type string. To apply conversion to parameter values use {@link #parse(String, java.util.Map)}
     *
     * @param command User defined command
     * @return CommandParser instance with parsed command
     */
    public static CommandParser parse(String command) {
        return new CommandParser(command, null);
    }

    /**
     * Creates CommandParser and performs parsing of User defined <code>command</code>, values of all parameters will
     * be converted using specified  <code>converters</code>
     *
     * @param command    User defined command
     * @param converters converters to be used for converting parameter values. Key is the name of parameter and value
     *                   is the converter that should be applied for parameter value
     * @return CommandParser instance with parsed command
     */
    public static CommandParser parse(String command, Map<String, TypeConverter> converters) {
        return new CommandParser(command, converters);
    }


    private String parse(Map<String, TypeConverter> converters, String cmd, Map<String, Object> parameterValues) {
        cmd = cmd.trim();
        int endIdx = cmd.indexOf(" ");
        if (endIdx == -1)
            return cmd;
        String cmdName = cmd.substring(0, endIdx);
        for (int idx = cmd.indexOf("=", endIdx); idx != -1; idx = cmd.indexOf("=", endIdx)) {
            String paramName = cmd.substring(endIdx + 1, idx).toLowerCase();
            char paramStartChar = ' ';
            while (paramStartChar == ' ') {
                paramStartChar = idx + 1 == cmd.length() ? '0' : cmd.charAt(idx + 1);
                idx++;
            }
            idx--;
            String paramValue;
            if (paramStartChar == '\'' || paramStartChar == '"') {
                StringBuilder buffer = new StringBuilder();
                int k = 0;
                int i = idx + 2;
                for ( int j = cmd.length(); i < j; i++) {
                    char c = cmd.charAt(i);
                    buffer.append(c);
                    if (c == paramStartChar) {
                        if (i + 1 >= j || cmd.charAt(i + 1) != paramStartChar) {
                            boolean isClosing = k % 2 == 0;
                            k += 2;
                            buffer.replace(buffer.length() - k / 2, buffer.length(), "");
                            k = 0;
                            if (isClosing) {
                                endIdx = i;
                                break;
                            }
                        } else k++;
                    }
                }
                if (endIdx != i)
                    throw new IllegalArgumentException("parameter value is quoted using " + paramStartChar +
                            " sign, and it should be closed with " + paramStartChar + " sign");
                paramValue = buffer.toString();
            } else {
                int nextEq = idx + 1 < cmd.length() ? cmd.indexOf("=", idx + 1) : -1;
                if (nextEq != -1 && !cmd.substring(idx + 1, nextEq).contains(" "))
                    idx--;
                endIdx = cmd.indexOf(" ", idx + 1);
                endIdx = endIdx == -1 ? cmd.length() : endIdx;
                paramValue = cmd.substring(idx + 1, endIdx);
            }
            parameterValues.put(paramName.trim(), paramValue);
        }
        normalizeParamValues(converters, cmdName, cmd, parameterValues);
        return cmdName;
    }

    private void normalizeParamValues(Map<String, TypeConverter> converters, String cmdName, String cmd,
                                      Map<String, Object> params) {
        if (params.isEmpty()) {
            int i = cmd.indexOf(cmdName);
            if (i >= 0) {
                i += cmdName.length();
                String value = cmd.substring(i).trim();
                if (value.length() != 0)
                    params.put(DEFAULT_PARAMETER_NAME, value);
            }
        }
        if (converters != null)
            TypeConverterHelper.applyConverter(params, converters);
    }

    /**
     * @return initially User defined command
     */
    public String getCommand() {
        return command;
    }

    /**
     * @return command name parsed from User defined command
     */
    public String getCommandName() {
        return cmdName;
    }

    /**
     * @return set of properties parsed from User defined command, there key of property is parameter name and value
     *         of property is parameter value
     */
    public Map<String, Object> getCommandParametersValues() {
        return parameterValues;
    }
}
